#define DDCLLUA_CORE

#include "lua-ddcldson.h"

static int
_push_value2state(lua_State * L, ddcl_DsonValue * d){
    int count = 0;
    switch(d->type){
    case DDCL_DSON_INTEGER:
        count ++;
        lua_pushinteger(L, d->u.integer);
        break;
    case DDCL_DSON_BOOLEAN:
        count ++;
        lua_pushboolean(L, d->u.integer);
        break;
    case DDCL_DSON_NUMBER:
        count ++;
        lua_pushnumber(L, d->u.number);
        break;
    case DDCL_DSON_STRING:
        count ++;
        lua_pushlstring(L, d->u.string, d->size);
        break;
    case DDCL_DSON_PTR:
        count += 1;
        lua_pushlightuserdata(L, d->u.ptr);
        break;
    case DDCL_DSON_MAP:
        {
            lua_newtable(L);
            ddcl_Dson * dson = d->u.dson;
            ddcl_DsonValue v;
            int key = 0;
            while(ddcl_next_dson(dson, &v)){
                _push_value2state(L, &v);
                if(key == 1){
                    lua_settable(L, -3);
                    key = 2;
                }
                else{
                    key = 1;
                }
            }
            if(key == 1){
                lua_pop(L, 1);
            }
            count ++;
        }
        break;
    case DDCL_DSON_ARRAY:
        {
            lua_newtable(L);
            ddcl_Dson * dson = d->u.dson;
            ddcl_DsonValue  v;
            int i = 1;
            while(ddcl_next_dson(dson, &v)){
                _push_value2state(L, &v);
                lua_seti(L, -2, i++);
            }
            count ++;
        }
        break;
    case DDCL_DSON_NULL:
        lua_pushnil(L);
        count ++;
        break;
    }
    return count;
}

static ddcl_Dson *
_push_state2dson (lua_State * L, ddcl_Dson * dson, int i, int * level, char ** err){
    if(*level >= 128){
        * err = "dson encode overlap level";
        return dson;
    }
    if (i < 0) {
        i = lua_gettop(L) + i + 1;
    }
    switch(lua_type(L, i)){
    case LUA_TNUMBER:{
        if(lua_isinteger(L, i)){
            dson = ddcl_push_dson_integer(dson, lua_tointeger(L, i));
        }else{
            dson = ddcl_push_dson_number(dson, lua_tonumber(L, i));
        }
        break;
    }
    case LUA_TBOOLEAN:{
        dson = ddcl_push_dson_boolean(dson, lua_toboolean(L, i));
        break;
    }
    case LUA_TSTRING:{
        size_t sz;
        const char * str = lua_tolstring(L, i, &sz);
        dson = ddcl_push_dson_string(dson, str, sz);
        break;
    }
    case LUA_TUSERDATA:{
        const void * p = lua_touserdata(L, i);
        size_t sz = luaL_checkinteger(L, ++ i);
        dson = ddcl_push_dson_string(dson, p, sz);
        break;
    }
    case LUA_TLIGHTUSERDATA:{
        const void * p = lua_touserdata(L, i);
        dson = ddcl_push_dson_ptr(dson, (void*)p, 0);
        break;
    }
    case LUA_TTABLE:{
        (*level) ++;
        ddcl_Dson * map = ddcl_new_dson(128);
        lua_pushnil(L);
        while (lua_next(L, i) != 0) {
            map = _push_state2dson(L, map, -2, level, err);
            if(*err){ ddcl_free_dson(map); return dson;}
            map = _push_state2dson(L, map, -1, level, err);
            if(*err){ ddcl_free_dson(map); return dson;}
            lua_pop(L, 1);
        }
        dson = ddcl_push_dson_map(dson, map);
        ddcl_free_dson(map);
        break;
    }
    case LUA_TNIL:
        dson = ddcl_push_dson_null(dson);
        break;
    }
    return dson;
}

DDCLLUA ddcl_Dson *
lddcl_encode_dson (lua_State * L,
        int start, int end, ddcl_Dson * dson, char ** err){
    for(int i = start; i <= end; i ++){
        int level = 0;
        dson = _push_state2dson(L, dson, i, &level, err);
        if(*err){ return dson; }
    }
    return dson;
}

DDCLLUA int
lddcl_decode_dson (lua_State * L, ddcl_Dson * dson){
    int count = 0;
    ddcl_begin_dson(dson);
    ddcl_DsonValue d;
    while(ddcl_next_dson(dson, &d)){
        if((count % 4) == 0){
            lua_checkstack(L, 4);
        }
        count += _push_value2state(L, &d);
    }
    return count;
}

static int
l_new (lua_State * L){
    size_t cap = luaL_checkinteger(L, 1);
    ddcl_Dson * dson = ddcl_new_dson(cap);
    lua_pushlightuserdata(L, dson);
    return 1;
}

static int
l_free (lua_State * L){
    luaL_checktype(L, 1, LUA_TLIGHTUSERDATA);
    ddcl_Dson * dson = lua_touserdata(L, 1);
    ddcl_free_dson(dson);
    return 0;
}

static int
l_clear (lua_State * L){
    luaL_checktype(L, 1, LUA_TLIGHTUSERDATA);
    ddcl_Dson * dson = lua_touserdata(L, 1);
    ddcl_clear_dson(dson);
    return 0;
}

static int
l_len (lua_State * L){
    luaL_checktype(L, 1, LUA_TLIGHTUSERDATA);
    ddcl_Dson * dson = lua_touserdata(L, 1);
    lua_pushinteger(L, ddcl_dson_len(dson));
    return 1;
}

static int
l_encode (lua_State * L){
    luaL_checktype(L, 1, LUA_TLIGHTUSERDATA);
    ddcl_Dson * dson = lua_touserdata(L, 1);
    char * err = NULL;
    dson = lddcl_encode_dson(L, 2, lua_gettop(L), dson, &err);
    lua_pushlightuserdata(L, dson);
    if(err){
        lua_pushstring(L, err);
    }else{
        lua_pushnil(L);
    }
    return 2;
}

static int
l_decode (lua_State * L){
    luaL_checktype(L, 1, LUA_TLIGHTUSERDATA);
    ddcl_Dson * dson = lua_touserdata(L, 1);
    return lddcl_decode_dson(L, dson);
}

static int
l_tostring (lua_State * L){
    luaL_checktype(L, 1, LUA_TLIGHTUSERDATA);
    ddcl_Dson * dson = lua_touserdata(L, 1);
    size_t len = ddcl_dson_len(dson);
    lua_pushlstring(L, dson, len);
    return 1;
}

static int
l_pstring (lua_State * L){
    size_t sz;
    const char * s = luaL_checklstring(L, 1, &sz);
    if(sz < DDCL_SIZEOF_DSON){
        return luaL_error(L, "dson string length error");
    }
    lua_pushlightuserdata(L, (void*)s);
    lua_pushinteger(L, sz);
    return 2;
}

static luaL_Reg _REG[] = {
    { NULL, NULL },
};
DDCLLUA int
luaopen_ddcl_dson (lua_State * L){
    luaL_checkversion(L);
    DDLUA_NEWLIB(L, "ddcl.dson", _REG);
    luapush_ddcl_dson(L, -1);
    return 1;
}

DDCLLUA int
luapush_ddcl_dson (lua_State * L, int i){
    lua_checkstack(L, 3);
    lua_pushvalue(L, i);

    DDLUA_PUSHFUNC(L, "new", l_new);
    DDLUA_PUSHFUNC(L, "free", l_free);
    DDLUA_PUSHFUNC(L, "clear", l_clear);
    DDLUA_PUSHFUNC(L, "len", l_len);
    DDLUA_PUSHFUNC(L, "encode", l_encode);
    DDLUA_PUSHFUNC(L, "decode", l_decode);
    DDLUA_PUSHFUNC(L, "tostring", l_tostring);
    DDLUA_PUSHFUNC(L, "pstring", l_pstring);

    lua_pop(L, 1);
    return 0;
}
